// Copyright 2011 Edward Wahlen. All Rights Reserved.

package org.wahlen.pc;

import org.wahlen.util.Pair;

/**
 * Combines items into some aggregate.
 * @param <T> item type
 * @param <A> aggregate type
 * @param <C> Combiner implementation type
 * @author edward.wahlen@gmail (Edward Wahlen)
 */
interface Combiner<T, A, C extends Combiner<T, A, C>> {

  void add(T item);

  void addAll(A combination);

  A combine();

  C newInstance();

  class IntSum implements Combiner<Integer, Integer, IntSum> {

    private int sum;

    @Override
    public void add(Integer item) {
      sum += item;
    }

    @Override
    public void addAll(Integer combination) {
      sum += combination;
    }

    @Override
    public Integer combine() {
      return sum;
    }

    @Override
    public IntSum newInstance() {
      return new IntSum();
    }
  }

  class Stats implements Combiner<Integer, Stats, Stats> {

    int count;
    int sum;
    int squareSum;

    public double mean() {
      return sum * 1D / count;
    }

    public double stdDev() {
      return stdDev(mean());
    }

    public double stdDev(double mean) {
      return Math.sqrt(squareSum * 1.D / count - mean * mean);
    }

    @Override
    public void add(Integer item) {
      count += 1;
      sum += item;
      squareSum += item * item;
    }

    @Override
    public void addAll(Stats other) {
      count += other.count;
      sum += other.sum;
      squareSum += other.squareSum;
    }

    @Override
    public Stats combine() {
      return this;
    }

    @Override
    public Stats newInstance() {
      return new Stats();
    }
  }

  class PairCombiner<T, A1, C1 extends Combiner<T, A1, C1>, A2, C2 extends Combiner<T, A2, C2>>
      extends Pair<C1, C2>
      implements Combiner<T, Pair<A1, A2>, PairCombiner<T, A1, C1, A2, C2>> {


    protected PairCombiner(C1 first, C2 second) {
      super(first, second);
    }

    public static <T, A1, C1 extends Combiner<T, A1, C1>, A2, C2 extends Combiner<T, A2, C2>>
    PairCombiner<T, A1, C1, A2, C2> from(C1 first, C2 second) {
      return new PairCombiner<T, A1, C1, A2, C2>(first.newInstance(), second.newInstance());
    }

    @Override
    public void add(T item) {
      first.add(item);
      second.add(item);
    }

    @Override
    public void addAll(Pair<A1, A2> combination) {
      first.addAll(combination.first);
      second.addAll(combination.second);
    }

    @Override
    public Pair<A1, A2> combine() {
      return Pair.of(first.combine(), second.combine());
    }

    @Override
    public PairCombiner<T, A1, C1, A2, C2> newInstance() {
      return new PairCombiner<T, A1, C1, A2, C2>(first.newInstance(), second.newInstance());
    }
  }
}
